import logging
import traceback
from typing import Iterable

from django.http import Http404
from rest_framework import status
from rest_framework.exceptions import ValidationError, Throttled, PermissionDenied, NotAuthenticated, NotFound, \
    AuthenticationFailed, MethodNotAllowed, NotAcceptable, UnsupportedMediaType
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import exception_handler

from common.response import R

logger = logging.getLogger("handlers")


class BusinessException(Exception):
    """业务异常"""
    # 默认异常信息
    default_message = "业务逻辑异常"
    # 默认结果编码
    default_result_code = 400
    # 默认HTTP响应编码
    default_status_code = status.HTTP_200_OK

    def __init__(self, message=default_message,
                 result_code=default_result_code,
                 status_code=default_status_code,
                 data=None):
        self.message = message
        self.result_code = result_code
        self.status_code = status_code
        # 响应数据
        self.data = R.make_std_response_data(result_code, message, data)

    def __str__(self):
        return self.message


def handle_validation_error(exc_data):
    """处理模型字段校验异常"""
    try:
        if isinstance(exc_data, dict):
            msg_list = []
            for field, v in exc_data.items():
                # 递归处理嵌套字段的校验异常
                temp_msg = handle_validation_error(v)
                if field == api_settings.NON_FIELD_ERRORS_KEY:
                    msg_list.append(temp_msg)
                else:
                    msg_list.append("字段{}:{}".format(field, temp_msg))
            error_msg = " ".join(msg_list)
        elif isinstance(exc_data, Iterable):
            error_msg = "，".join(exc_data)
        else:
            error_msg = str(exc_data)
    except:
        error_msg = "参数校验不通过"
    return error_msg


def custom_exception_handler(exc, context):
    # Call REST framework's default exception handler first,
    # to get the standard error response.
    response = exception_handler(exc, context)

    if isinstance(exc, BusinessException):
        # 处理自定义业务异常
        logger.error(f"【业务异常】简要信息：{exc.message}，\ntraceback：{traceback.format_exc()}")
        return R(data=exc.data, status=exc.status_code)

    error_msg = None
    result_code = None
    if isinstance(exc, ValidationError):
        # 处理模型字段校验异常
        logger.debug(f"【参数校验异常】简要信息：{exc.detail}，\ntraceback：{traceback.format_exc()}")
        error_msg = handle_validation_error(exc.detail)
        error_msg = f'参数校验失败，{error_msg}'

    elif isinstance(exc, Throttled):  # check that a Throttled exception is raised
        error_msg = '请求频率超过最大限制：request limit exceeded %d seconds' % exc.wait
        result_code = 429

    elif isinstance(exc, PermissionDenied):
        error_msg = 'You do not have permission to perform this action.'
        result_code = 403

    elif isinstance(exc, NotAuthenticated):
        error_msg = '没有提供认证：Authentication credentials were not provided.'
        result_code = 401

    elif isinstance(exc, NotFound) or isinstance(exc, Http404):
        error_msg = 'Not found.'
        result_code = 404

    elif isinstance(exc, AuthenticationFailed):
        error_msg = '认证失败，认证凭证不正确/过期，请重新登录'
        result_code = 401

    elif isinstance(exc, MethodNotAllowed):
        error_msg = 'Method not allowed.'
        result_code = 405

    elif isinstance(exc, NotAcceptable):
        error_msg = 'Could not satisfy the request Accept header.'
        result_code = 405

    elif isinstance(exc, UnsupportedMediaType):
        error_msg = 'Unsupported media type in request.'
        result_code = 415

    elif not response and isinstance(exc, Exception):
        logger.error(f"【系统异常】{traceback.format_exc()}")
        error_msg = '系统异常，操作失败'
        result_code = 500

    if error_msg is not None and result_code is not None:
        if not response:
            return R.error(message=error_msg, code=result_code)
        else:
            response.data = R.make_std_response_data(result_code, message=error_msg)

    return response
